#include <iostream>
#include <fstream>
#include <cstring>
#include <sstream>

#include "tinyxml.h"

#include "ItemModule.h"

#define ITEMS_ADDR		0x155200

using namespace std;

const char* ItemModule::getName() {
  return "Items";
}

const char* ItemModule::getCredits() {
  return "Written by MrTenda\nBased on the JHack Item Editor";
}

void ItemModule::readFromRom(Rom &rom) {
  int i;
  uchar ownership;
  rom.seek(ITEMS_ADDR);
  for (i = 0; i < NUM_ITEMS; i++) {
    readRegString(rom, items[i].name, 25);
    items[i].type = rom.read();
    items[i].cost = rom.readMulti(2);

    ownership = rom.read();
    items[i].canUse[0] = ownership & 1;		// bit 0
    items[i].canUse[1] = ownership & 2;		// bit 1
    items[i].canUse[2] = ownership & 4;		// bit 2
    items[i].canUse[3] = ownership & 8;		// bit 3
    items[i].isTransformable = ownership & 0x10;	// bit 4
    items[i].canGive = !(ownership & 0x20);	// bit 5
    items[i].unknown = ownership & 0x40;		// bit 6
    items[i].infinite = !(ownership & 0x80);	// bit 7

    items[i].effect = rom.readMulti(2);
    items[i].special[0] = rom.read();
    items[i].special[1] = rom.read();
    items[i].special[2] = rom.read();
    items[i].special[3] = rom.read();
    items[i].descAddress = rom.readMulti(4);
  }
}

void ItemModule::writeToRom(Rom &rom) {
  int i;
  uchar ownership;
  rom.seek(ITEMS_ADDR);
  for (i = 0; i < NUM_ITEMS; i++) {
    writeRegString(rom, items[i].name, 25);
    rom.write(items[i].type);
    rom.writeMulti(items[i].cost, 2);
    ownership = items[i].canUse[0] | (items[i].canUse[1] << 1) | (items[i].canUse[2] << 2) | (items[i].canUse[3] << 3)
                  | (items[i].isTransformable << 4) | ((!(items[i].canGive)) << 5) | (items[i].unknown << 6)
		  | ((!(items[i].infinite)) << 7);
    rom.write(ownership);
    rom.writeMulti(items[i].effect, 2);
    rom.write(items[i].special[0]);
    rom.write(items[i].special[1]);
    rom.write(items[i].special[2]);
    rom.write(items[i].special[3]);
    rom.writeMulti(items[i].descAddress, 4);
  }
}

#define NUM_TYPE_NAMES		14

const int itemTypeNums[] {
  4, 8,
  16, 17,
  20, 24, 28,
  32, 36, 40, 44,
  48, 52, 59
};

const char* itemTypeNames[] {
  "NPC Item", "Broken Item",
  "Bash Weapon", "Shoot Weapon",
  "Body Armor", "Arm Armor", "Other Armor",
  "Food", "Drink", "Condiment", "Shared Food",
  "Medicine", "Battle Item", "Key Item"
};

const char* itemAttrNames[][4] {
  { "Unknown1", "Unknown2", "Unknown3", "Unknown4" },
  { "PlayableSprite", "Unknown2", "Unknown3", "Unknown4" },
  { "Unknown1", "RequiredIQ", "FixedItemID", "Unknown4" },
  { "Offense", "PooOffense", "Guts", "MissRate" },
  { "Offense", "PooOffense", "Guts", "MissRate" },
  { "Defense", "PooDefense", "Speed", "Protection" },
  { "Defense", "PooDefense", "Luck", "Protection" },
  { "Defense", "PooDefense", "Luck", "Protection" },
  { "RecoveryType", "Increase", "PooIncrease", "SkipSandwichTime" },
  { "RecoveryType", "Increase", "PooIncrease", "SkipSandwichTime" },
  { "RecoveryType", "Increase", "PooIncrease", "SkipSandwichTime" },
  { "RecoveryType", "Increase", "PooIncrease", "SkipSandwichTime" },
  { "Unknown1", "Unknown2", "Unknown3", "Unknown4" },
  { "Unknown1", "Unknown2", "Unknown3", "Unknown4" },
  { "Unknown1", "Unknown2", "Unknown3", "Unknown4" }
};

int ItemModule::getTypeName(int type) {
  int i;
  for (i = 0; i < NUM_TYPE_NAMES; i++)
    if (type == itemTypeNums[i])
      return i;

  return -1;
}

void ItemModule::readFromDat(char* dirName, map<string, string> &fns, map<string, int> &labels) {
  char buf[100];
  getDatFname(fns, "items", "items.xml", dirName, buf);
 
  // Load XML document
  TiXmlDocument doc(buf);
  if (!doc.LoadFile()) {
    cerr << "Error: (" << getName() << ") Failed to load " << buf << endl;
    return;
  }
  TiXmlHandle hDoc(&doc);
  TiXmlElement* elem;
  TiXmlHandle hRoot(0), hItem(0);

  // Get root element
  elem = hDoc.FirstChildElement().Element();
  hRoot = TiXmlHandle(elem);

  //Traverse Characters
  TiXmlElement* itemNode = hRoot.FirstChild("Item").Element();
  TiXmlElement* attrNode;
  const char* elemKey;
  int itemN, i, j;
  for (; itemNode; itemNode = itemNode->NextSiblingElement()) {
    itemN = strToInt(itemNode->Attribute("id"));
    hItem = TiXmlHandle(itemNode);
    attrNode = hItem.FirstChild().Element();
    for (; attrNode; attrNode = attrNode->NextSiblingElement()) {
      elemKey = attrNode->Value();
      if (strcmp(elemKey, "Name") == 0)
        strncpy(items[itemN].name,attrNode->GetText(),25);
      else if (strcmp(elemKey, "Effect") == 0)
        items[itemN].effect = strToInt(attrNode->GetText());
      else if (strcmp(elemKey, "Cost") == 0)
        items[itemN].cost = strToInt(attrNode->GetText());
      else if (strcmp(elemKey, "Type") == 0) {
        for (i=0; i<NUM_TYPE_NAMES; i++)
          if (strcmp(attrNode->GetText(), itemTypeNames[i]) == 0) {
            items[itemN].type = itemTypeNums[i];
            break;
          }
        if (i==NUM_TYPE_NAMES) // No valid type name found
          items[itemN].type = strToInt(attrNode->GetText());
      } else if (strcmp(elemKey, "HelpText") == 0)
        items[itemN].descAddress = userStrToSnesAddr(attrNode->GetText(), labels);
      else if (strcmp(elemKey, "CanUse") == 0) {
        items[itemN].canUse[0] = attrNode->Attribute("ness")[0] == '1';
        items[itemN].canUse[1] = attrNode->Attribute("paula")[0] == '1';
        items[itemN].canUse[2] = attrNode->Attribute("jeff")[0] == '1';
        items[itemN].canUse[3] = attrNode->Attribute("poo")[0] == '1';
      } else if (strcmp(elemKey, "CanGive") == 0)
        items[itemN].canGive = attrNode->GetText()[0] == '1';
      else if (strcmp(elemKey, "Unknown5") == 0)
        items[itemN].unknown = attrNode->GetText()[0] == '1';
      else if (strcmp(elemKey, "Transformable") == 0)
        items[itemN].isTransformable = attrNode->GetText()[0] == '1';
      else if (strcmp(elemKey, "InfiniteUse") == 0)
        items[itemN].infinite = attrNode->GetText()[0] == '1';
      else if (strcmp(elemKey, "Protection") == 0) {
        items[itemN].special[3] = strToInt(attrNode->Attribute("fire"));
        items[itemN].special[3] += strToInt(attrNode->Attribute("freeze")) << 2;
        items[itemN].special[3] += strToInt(attrNode->Attribute("flash")) << 4;
        items[itemN].special[3] += strToInt(attrNode->Attribute("paralysis")) << 6;
      } else { // One of the "special" values
        for (i=0; i<NUM_TYPE_NAMES; i++) {
          for (j=0; j<4; j++) {
            if (strcmp(elemKey, itemAttrNames[i][j]) == 0) {
              items[itemN].special[j] = strToInt(attrNode->GetText());
              break;
            }
          }
          if (j != 4)
            break;
        }
      }
    }
  }
}

//void ItemModule::writeToDat(char* dirName) {
void ItemModule::writeToDat(char* dirName, map<string, string> &fns) {
  int i,j;
  char buf[100];

  TiXmlDocument doc;
  TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "", "" );
  doc.LinkEndChild( decl );

  TiXmlElement* root = new TiXmlElement("Items");
  doc.LinkEndChild(root);

  TiXmlElement* item;
  TiXmlElement* elem;
  int typeName;
  stringstream ss;
  for (i=0; i<NUM_ITEMS; i++) {
    item = new TiXmlElement("Item");
    item->SetAttribute("id", i);
    root->LinkEndChild(item);

    elem = new TiXmlElement("Name");
    elem->LinkEndChild( new TiXmlText( items[i].name ));
    item->LinkEndChild(elem);

    elem = new TiXmlElement("Effect");
    ss.str(std::string());
    ss << ((int) items[i].effect);
    elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
    item->LinkEndChild(elem);

    elem = new TiXmlElement("Cost");
    ss.str(std::string());
    ss << (int) items[i].cost;
    elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
    item->LinkEndChild(elem);

    elem = new TiXmlElement("Type");
    typeName = getTypeName(items[i].type);
    if (typeName == -1) {
      ss.str(std::string());
      ss << (int) items[i].type;
      elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
      item->LinkEndChild(elem);

      for (j=0; j<4; j++) {
        elem = new TiXmlElement(itemAttrNames[0][j]);
        ss.str(std::string());
        ss << (int) items[i].special[j];
        elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
        item->LinkEndChild(elem);
      }
    } else {
      elem->LinkEndChild( new TiXmlText( itemTypeNames[getTypeName(items[i].type)] ));
      item->LinkEndChild(elem);

      for (j=0; j<4; j++) {
        elem = new TiXmlElement(itemAttrNames[typeName+1][j]);
        if ((j==3) && ((items[i].type == 20) || (items[i].type == 24) || (items[i].type == 28))) {
          // Protection
          elem->SetAttribute("fire", (items[i].special[3]) & 3);
          elem->SetAttribute("freeze", (items[i].special[3] >> 2) & 3);
          elem->SetAttribute("flash", (items[i].special[3] >> 4) & 3);
          elem->SetAttribute("paralysis", (items[i].special[3] >> 6) & 3);
        } else {
          ss.str(std::string());
          ss << (int) items[i].special[j];
          elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
        }
        item->LinkEndChild(elem);
      }
    }

    elem = new TiXmlElement("HelpText");
    ss.str(std::string());
    ss << hex << "$" << (int) items[i].descAddress << dec;
    elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
    item->LinkEndChild(elem);


    elem = new TiXmlElement("CanUse");
    elem->SetAttribute("ness", items[i].canUse[0]);
    elem->SetAttribute("paula", items[i].canUse[1]);
    elem->SetAttribute("jeff", items[i].canUse[2]);
    elem->SetAttribute("poo", items[i].canUse[3]);
    item->LinkEndChild(elem);

    elem = new TiXmlElement("CanGive");
    ss.str(std::string());
    ss << items[i].canGive;
    elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
    item->LinkEndChild(elem);

    elem = new TiXmlElement("Unknown5");
    ss.str(std::string());
    ss << items[i].unknown;
    elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
    item->LinkEndChild(elem);

    elem = new TiXmlElement("Transformable");
    ss.str(std::string());
    ss << items[i].isTransformable;
    elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
    item->LinkEndChild(elem);

    elem = new TiXmlElement("InfiniteUse");
    ss.str(std::string());
    ss << items[i].infinite;
    elem->LinkEndChild( new TiXmlText( ss.str().c_str() ));
    item->LinkEndChild(elem);
  }

  getDatFname(fns, "items", "items.xml", dirName, buf);
  doc.SaveFile(buf);
}
